Win32API【1】

您所在的位置:网站首页 delphi 窗口句柄 Win32API【1】

Win32API【1】

#Win32API【1】| 来源: 网络整理| 查看: 265

本节是个大难关,一定要多看,多敲代码理解 0. 关于在DevC++上的一些准备新建项目修改项目属性项目的重要性 教程1. 初识WinMain函数2. 让系统知道我们要弄一个窗口——注册窗口类3. 创建窗口——CreateWindow函数4. 萌新噩梦:消息循环与回调函数创建消息循环绑定回调函数 完整代码示例

所有代码文件均已放到蓝奏云网盘,下载解压即可运行! WIN32API示例 (DEVC++) 密码:dgaf

在这里插入图片描述 Win32api是一个很老的Native API,早在Windows95年代(1995) 就有了,微软对它整个库的更新是到2002年 (也就是Windows XP 之后该库基本上不再改动了 ,但是这还不够,微软在XP之后要对WinAPI进行扩充,于是就有了后来的 d2d1.h,d3d12.h,mfplay.h 等等)

Windows.h 囊括了WinAPI几乎所有的函数与宏定义,然而实际上源码只有200来行 ,为什么? 在这里插入图片描述 函数都是在这些头文件里声明的!

各位C语言党不要担心,Win32API 是用纯C写的,没有C++的晦涩语法 (毕竟C++党都去MFC与QT了),虽然是老工具,可是放眼现在照样有人用

本系列教程我们不用VC (VC的确方便啊,该链的库直接唰唰唰帮你弄好,不用怕什么链接错误又要修改项目属性之类的) ,使用小熊猫 DevC++ 来完成Windows编程之旅!

0. 关于在DevC++上的一些准备 新建项目

打开小熊猫 DevC++,文件->新建->新建项目->选择空项目 在这里插入图片描述 在这里插入图片描述

之后如果要打开项目,只需 点击.dev文件 即可 在这里插入图片描述

修改项目属性

打开项目管理,修改项目属性:将类型改成 Win32图形界面程序(GUI) 在这里插入图片描述

在这里插入图片描述

项目的重要性

DevC++是支持单文件编译的 ,有人说,我们可以只用一个.c文件来做啊,为什么要拐弯抹角建一个项目呢? 其实,学到后面你就会发现,如果使用单个.c文件,管理资源很不方便 (例如 头文件,资源文件,regex文件等等) ,要处理这些麻烦问题最后还不是要弄项目? 所以,我想对学C的萌新说一句,早学项目早受益,之后我们还会涉及大量的项目管理

教程 1. 初识WinMain函数

我们需要抛弃陪伴我们萌新很久的main函数,改成WinMain函数

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance, LPSTR lpCMDline, int nCMDshow) { }

你会发现我们需要写这么大一串!用main函数不就更好? 事实上,我们只需要用第一个参数 hinstance , 然后把它改成:

int WINAPI WinMain(HINSTANCE hin,HINSTANCE,LPSTR,int) { }

这样就简洁多了,关于WinMain的参数,我们只需要理解第一个参数 hinstance

hinstance :应用程序当前实例的句柄。 在这里插入图片描述 我们知道,一个程序光有一个窗口不行,我们不仅要有若干个子窗口来丰富我们的程序,更需要很多图片音乐放进程序里锦上添花,可是如何才能将这些东西统一成一个整体呢? 实例句柄(hinstance) 因此就出来了

另外,这个hinstance是系统给我们的,不用我们瞎操心,照着用就行 就好像医院妇产科,系统都会给每一个新生儿一个hinstance,不然没hinstance很容易把别人家孩子搞混。hinstance的存在也正是如此,它可以唯一标识应用程序

2. 让系统知道我们要弄一个窗口——注册窗口类

首先,我们需要让系统知道我们要弄窗口,在WinMain函数写上

// WNDCLASS 窗口类信息,是个结构体 WNDCLASS wc = {}; wc.hInstance = hin; //实例句柄 wc.lpszClassName = "MY_Window"; //窗口类名 RegisterClass(&wc); //注册窗口 // RegisterClass() 注册窗口类 // 接受一个 WNDCLASS 的指针 // 必须通过这一步, 窗口才能注册成功

你会发现,我们要向系统传一个叫 “WNDCLASS” 窗口类 的东西,这个东西是什么呢? 我们在上面讲到,Hinstance实例句柄作为指向一个应用程序实例的东西,它不仅包括了窗口,还包括该应用程序的图片音乐等等很多东西,包这么多东西,如果我在外面,只是找里面的窗口岂不是很麻烦?

窗口类(WNDClass) 于是就出来了,你可以理解为 一个程序里面所有窗口的集合

窗口类毕竟是一个集合啊!那我们怎么表示单个窗口? 窗口句柄(HWND) 出来了,实际上是一个窗口在系统的唯一ID

下面是三者的关系:

在这里插入图片描述

之后我们填完WNDCLASS结构体,就可以通过 RegisterClass函数 告诉系统我们要弄窗口了。

3. 创建窗口——CreateWindow函数

我们注册窗口类之后,就可以 真正弄个窗口 了

// CreateWindow 创建窗口,返回一个窗口句柄 HWND hwnd = CreateWindow( "M_CLASS", //窗口类名,要和上文的 lpszClassName 相同,否则返回NULL "我的第一个程序", //窗口标题 WS_OVERLAPPEDWINDOW|WS_VISIBLE, //窗口样式 0, //窗口左上角横坐标 0, //窗口左上角纵坐标 640, //窗口宽度 480, //窗口高度 NULL, //父窗口 (HWND) NULL, //菜单句柄 (HMENU) hin, //实例句柄 (HINSTANCE) ,不填会返回NULL NULL //附加参数 );

可能这是C语言萌新见过的最长的函数,开场11个参数,而且都要填! 但是,我们最常用的也还是那几个参数罢了!

这里说明一下第3个参数窗口样式,第三个参数指定了我们窗口是咋样的

WS_OVERLAPPEDWINDOW 这个包括了一个常见窗口所需的所有样式,直接记就行了! WS_VISIBLE 这个指定我们的窗口是否可见

更多样式可以参考这位大神的文章: tanyufeng_521:Windows窗口风格详细解释

创建完之后,我们的程序就差一步即可大功告成,也是基础中最难的一步:消息循环!

4. 萌新噩梦:消息循环与回调函数

这里先放图

在这里插入图片描述

创建消息循环

WinAPI的窗口难道只是创建就行了吗?大错特错! 你会发现,窗口会一闪而过!程序也完成任务回家了。 如何才能让窗口一直显示? 这个时候,消息循环 的重要作用就很好的彰显出来了!

//消息循环的构建 MSG msg = {}; //消息结构体 while(GetMessage(&msg,NULL,0,0)>0) // 如果没有发生错误,且收到了任意消息... { ::TranslateMessage(&msg); // 翻译消息,将消息中的键盘码转换为对应的字符 ::DispatchMessage(&msg); // 派发消息,调用 CallBackFunc 回调函数处理消息 }

借助这个循环,程序就可以一直发出消息,阻止程序自身退出 只要用户不按窗口右上角的退出按钮!程序就会一直运行!

问题是,当你把这个循环打上去之后,你就会发现,窗口连影都没有 那么,我们可以使用 DefWindowProc() 返回一个窗口

但是,如何用呢?我们发现,把 DefWindowProc() 放进while循环里竟然不起作用!

原来,是我们没有处理 DefWindowProc 返回的窗口! 那么我们应该如何处理呢?

你会发现,我们少讲了什么…

绑定回调函数

没错,就是 回调函数 (CallBackFunction) 我们可以使用回调函数,对 DefWindowProc 返回的窗口进行处理 第一次看 回调 (CallBack) 这个概念的时候萌新肯定会不理解,其实在生活中回调是触手可及的!

就比如说,你现在肚子很饿,直接下馆子,当你点完餐以后,你就会不停的询问服务员什么时候可以上菜,我快饿死了!!! 这时候,服务员肯定会不停地告诉你上菜时间!

不停的操作与获得更新结果,就是回调的概念 有了回调函数,我们就可以保证窗口一直显示!

//回调函数, 返回一个窗口 LRESULT CALLBACK CallBackFunc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam) { // hwnd 从主函数获得的窗口句柄 // msg 从主函数的消息循环获得的消息 // wParam 附加参数1 // lParam 附加参数2 // C语言Review: // switch用于对一个整数进行判断,并跳到相应的case语句块 // 会一直执行直至break或switch语句块的末尾 // 所以写完一个case 随手加break是一个必须养成的习惯 // 不然会执行意想不到的结果 switch(msg) //对msg进行判断,进switch语句块 { case WM_DESTROY: //如果要退出 PostQuitMessage(0); //传递退出消息,终止主函数的消息循环 break; // 默认行为(什么都不做,就返回窗口) // 这个地方,很多萌新都容易漏,导致窗口不显示又退不出循环,注意一下 default: return DefWindowProc(hwnd,msg,wParam,lParam); } }

代码中我们使用了switch语句,对回调函数的msg消息进行处理 然后再根据消息进行操作或返回窗口

高一的时候我开始学做窗口,就是被这消息循环难住了,导致当时还要死记硬背 (当时没理解,只好死记硬背,现在看来完全没必要,果真编程还是注重思维培养的)

消息循环还是得多看,如果你能理解上面那张流程图,那么这样的框架你也能唾手可得 经历了那么多曲折,现在我们终于可以做一个窗口了。

在这里插入图片描述

完整代码示例 // main.cpp #include //回调函数 LRESULT CALLBACK CallBackFunc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam) { // hwnd 从主函数获得的窗口句柄 // msg 从主函数的消息循环获得的消息 // wParam 附加参数1 // lParam 附加参数2 // C语言Review: // switch用于对一个整数进行判断,并跳到相应的case语句块 // 会一直执行直至break或switch语句块的末尾 // 所以写完一个case 随手加break是一个必须养成的习惯 // 不然会执行意想不到的结果 switch(msg) //对msg进行判断,进switch语句块 { case WM_DESTROY: //如果要退出 PostQuitMessage(0); //传递退出消息,终止主函数的消息循环 break; // 默认行为(什么都不做,就返回窗口) // 这个地方,很多萌新都容易漏,导致窗口不显示又退不出循环,注意一下 default: return DefWindowProc(hwnd,msg,wParam,lParam); } return 0; } int WINAPI WinMain(HINSTANCE hin,HINSTANCE,LPSTR,int) { // WNDCLASS 窗口类信息,是个结构体 // 下面这三个参数是必填的 WNDCLASS wc = {}; wc.hInstance = hin; //实例句柄 wc.lpfnWndProc = CallBackFunc; //要绑定的回调函数,函数名就是函数的地址 wc.lpszClassName = "MY_Window"; //窗口类名 RegisterClass(&wc); //注册窗口 // RegisterClass() 注册窗口类 // 接受一个 WNDCLASS 的指针 // 必须通过这一步, 窗口才能注册成功 // CreateWindow 创建窗口,返回一个窗口句柄 HWND hwnd = CreateWindow( "MY_Window", //窗口类名,要和上文的 lpszClassName 相同,否则返回NULL "我的第一个程序", //窗口标题 WS_OVERLAPPEDWINDOW|WS_VISIBLE, //窗口样式 0, //窗口左上角横坐标 0, //窗口左上角纵坐标 640, //窗口宽度 480, //窗口高度 NULL, //父窗口 (HWND) NULL, //菜单句柄 (HMENU) hin, //实例句柄 (HINSTANCE) ,不填会返回NULL NULL //附加参数 ); //消息循环的构建 MSG msg = {}; //消息结构体 while(GetMessage(&msg,NULL,0,0)>0) // 如果没有发生错误,且收到了任意消息... { ::TranslateMessage(&msg); // 翻译消息,将消息中的键盘码转换为对应的字符 ::DispatchMessage(&msg); // 派发消息,调用 CallBackFunc 回调函数处理消息 } return 0; }

在这里插入图片描述

我是DGAF,我们下个教程见



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3